#include<windowsx.h>
#include <Mferror.h>
#include "Form.h"
#include "ResDef.h"
#include "MFPlayer.h"
#include "CustomException.h"

namespace core{
    namespace form
    {
        namespace tool = util::tool;    
        namespace ep = util::exception;

        Mainform_component_struct::~Mainform_component_struct(){
            if(pRight_click_menu != nullptr)
                delete pRight_click_menu;
        }
        
        component::MenuInstance Mainform_component_struct::GetRootMenu(){
            return dynamic_cast<component::Menu*>(pRight_click_menu->root->data)
                ->CreateInstance();
        }

        FormFactory::FormFactory(LPVOID app, WndClass wndClass, HINSTANCE hInstance){
            if(!hasWndClassRegistered)
                registerWndClass(hInstance);
            this->lpWindowName = TEXT("default");
            this->wndClass = wndClass;
            this->hInstance = hInstance;
            this->lpApp = app;
            this->dwStyle = WS_OVERLAPPEDWINDOW;
            this->hWndParent = NULL;
            this->hMenu = NULL;
            this->X = 0;
            this->Y = 0;
            this->nHeight = 800;
            this->nWidth = 1000;
        }

        void FormFactory::registerWndClass(HINSTANCE hInstance){
            classRegistered[WndClass::main_form].cbSize        = sizeof(WNDCLASSEX);
            classRegistered[WndClass::main_form].style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
            classRegistered[WndClass::main_form].lpfnWndProc   = MainForm::WndProc;
            classRegistered[WndClass::main_form].cbClsExtra    = 0;
            classRegistered[WndClass::main_form].cbWndExtra    = sizeof(LONG_PTR);
            classRegistered[WndClass::main_form].hInstance     = hInstance;
            classRegistered[WndClass::main_form].hbrBackground = NULL;
            classRegistered[WndClass::main_form].lpszMenuName  = NULL;
            classRegistered[WndClass::main_form].hCursor       = LoadCursor(NULL, IDI_APPLICATION);
            classNameGroup[WndClass::main_form] = TEXT("MainForm");
            classRegistered[WndClass::main_form].lpszClassName = classNameGroup[main_form];
            if(!RegisterClassEx(&classRegistered[main_form])){
                util::tool::PrintError();
            }
        }
        void FormFactory::UnregisterWndClass(){
            for(int i = 0; i < WndClass::_COUNT; i++){
                UnregisterClass(classNameGroup[i], classRegistered[i].hInstance);
            }
        }

        void FormFactory::SetWndSize(int w, int h){
            nWidth = w;
            nHeight = h;
        }

        void FormFactory::SetStyle(DWORD style){
            this->dwStyle = style;
        }

        HRESULT FormFactory::CreateForm(AbstractForm ** pform){
            if(*pform != nullptr){
                delete *pform;
            }
            HWND hWnd;
            hWnd = CreateWindow(
                classNameGroup[wndClass],
                lpWindowName,
                dwStyle,
                X,
                Y,
                nWidth,
                nHeight,
                hWndParent,
                hMenu,
                hInstance,
                pform
            );
            if(!hWnd){
                util::tool::PrintError();
                return E_FAIL;
            }
            switch (wndClass)
            {
            case WndClass::main_form :
            default:
                MainForm * form = new MainForm(lpWindowName, hWnd, nWidth, nHeight);
                HRESULT hr;
                form->CreateDeviceIndependentResources();
                hr = form->CreateDeviceResources();
                *pform = static_cast<AbstractForm *>(form);
                break;
            }
            
            (*pform)->SetMessageHandlerToApp(reinterpret_cast<form_msg::IMessageHandler*>(lpApp));
            return S_OK;
        }

        AbstractForm::AbstractForm(LPCTSTR windowName, HWND hwnd, int w, int h) :
            name(windowName),
            hWnd(hwnd)
        {
        }

        AbstractForm::~AbstractForm(){
        }

        HRESULT AbstractForm::ShowForm(){
            WINBOOL r = 1;
            // Because the SetWindowPos function takes its size in pixels, we
            // obtain the window's DPI, and use it to scale the window size.
            float dpi = GetDpiForWindow(hWnd);
            RECT wnd_rect;
            GetWindowRect(hWnd, &wnd_rect);

            SetWindowPos(
                hWnd,
                NULL,
                0,
                0,
                static_cast<int>(ceil((wnd_rect.right - wnd_rect.left) * dpi / 96.f)),
                static_cast<int>(ceil((wnd_rect.bottom - wnd_rect.top) * dpi / 96.f)),
                SWP_NOMOVE);
            ShowWindow(hWnd, SW_SHOWNORMAL);
            UpdateWindow(hWnd);

            if(r)
                return S_OK;
            else{
                util::tool::PrintError();
                return E_FAIL;
            }
        }

        WINBOOL AbstractForm::GetWndRect(LPRECT lprect){
            return GetWindowRect(hWnd, lprect);
        }

        WINBOOL AbstractForm::Move(int x, int y){
            RECT current_rect;
            GetWindowRect(hWnd, &current_rect);
            float dpi = GetDpiForWindow(hWnd);
            return SetWindowPos(
                hWnd,
                NULL,
                ceil(current_rect.left + x),
                ceil(current_rect.top + y),
                0,
                0,
                SWP_NOSIZE
            );
        }

        BOOL AbstractForm::UpdateForm(){
            HRESULT hr = InvalidateRect(hWnd, NULL, true);
            return UpdateWindow(hWnd);
        }

        void AbstractForm::SetMessageHandlerToApp(form_msg::IMessageHandler * pApp){
            pIapp = pApp;
        }
        
        bool AbstractForm::MsgSendToApp(form_msg::MessageType msg, void * param){
            if(pIapp == nullptr)
                throw ep::UnexpectedException("message handler for app has not been set");
            return pIapp->Receive(msg, param);
        }

        AbstractD2DForm::AbstractD2DForm(LPCTSTR windowName, HWND hWnd, int w, int h) : 
            AbstractForm(windowName, hWnd, w, h),
            pDirect2dFactory(NULL),
            pRenderTarget(NULL){}
        
        HRESULT AbstractD2DForm::CreateDeviceIndependentResources()
        {
            HRESULT hr = S_OK;
            // Create a Direct2D factory.
            hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pDirect2dFactory);
            return hr;
        }

        
        HRESULT AbstractD2DForm::CreateDeviceResources(){
            RECT rc;
            GetClientRect(hWnd, &rc);
            D2D1_SIZE_U size = D2D1::SizeU(
                rc.right - rc.left,
                rc.bottom - rc.top);
            // Create a Direct2D render target.
            return pDirect2dFactory->CreateHwndRenderTarget(
                D2D1::RenderTargetProperties(
                    D2D1_RENDER_TARGET_TYPE_DEFAULT,
                    D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)
                    ),
                D2D1::HwndRenderTargetProperties(hWnd, size),
                &pRenderTarget); 
        }

        AbstractD2DForm::~AbstractD2DForm(){
            tool::SafeRelease(&pDirect2dFactory);
            tool::SafeRelease(&pRenderTarget);
        }

        MainForm::MainForm(LPCTSTR windowName, HWND hwnd, int w, int h) : 
            AbstractD2DForm(windowName, hwnd, w, h),
            ani_manager(),
            pComponents(nullptr),
            pControl(nullptr),
            pMain_audio_player(nullptr),
            test_player(nullptr){

            pControl = new MAIN_CONTROL_STRUCT{
                POINTS{SHRT_MIN,SHRT_MIN},
                clock()
            };
            pComponents = new MAIN_COMPONENTS();

            component::CreateMenuTree(
                component::MenuID::MAIN_RIGHT_CLICK_POPMENU,
                TEXT("root"),
                &pComponents->pRight_click_menu,
                true
            );
            component::AddMenuItem(
                component::MenuItemID::EXIT_PROGRAM,
                TEXT("exit"),
                &pComponents->pRight_click_menu->root
            );


            display::EnableAlphaCompositing(hWnd);
        }

        HRESULT MainForm::CreateDeviceResources(){
            HRESULT hr = AbstractD2DForm::CreateDeviceResources();
            if(SUCCEEDED(hr)){
                //test
                display::AnimationProcess * ani = new display::AnimationProcess();
                ani->SetRender(pRenderTarget);
                D2D1_RECT_F rect = D2D1::RectF(0,0,300,300);
                ani->SetRect(rect);
                hr = ani->Prepare(RES_DEfINE_BMP_CBDRGN01, RES_DEfINE_BMP_CBDRGN33);

                util::resource::BinFile video_file;
                display::mf::MFPlayer * mf_player;
                display::mf::MFPlayer::CreateMFPlayer(hWnd, &mf_player);
                util::resource::ReadBinFileFromStaticMemory(
                    &_binary_resbuilder_resource_data_start,
                    video_file,
                    RES_DEfINE_MP4_amns
                );
                if(SUCCEEDED(hr))
                    hr = mf_player->PrepareSource(&video_file);
                if(SUCCEEDED(hr))
                    hr =mf_player->PrepareToPlay();

                if(SUCCEEDED(hr)){
                    pMain_audio_player = static_cast<IPlayer *>(ani);
                    test_player = static_cast<IPlayer *>(mf_player);
                }
            }
            return hr;
        }

        MainForm::~MainForm(){
            if(pMain_audio_player != nullptr)
                delete pMain_audio_player;

            if(test_player != nullptr)
                (dynamic_cast<display::mf::MFPlayer*>(test_player))->Release();

            if(pControl != nullptr)
                delete pControl;
            if(pComponents != nullptr)
                delete pComponents;
        }

        LRESULT CALLBACK MainForm::origin_wnd_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
        {
            LRESULT result = 0;

            if (message == WM_CREATE)
            {
                LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;

                //暂时的解决方案
                LONG_PTR dwstyle = ::GetWindowLongPtr(hwnd, GWL_STYLE);
                if(dwstyle & WS_CAPTION)
                    dwstyle = dwstyle &~WS_CAPTION;
                ::SetWindowLongPtr(hwnd, GWL_STYLE, dwstyle);

                LONG_PTR form = reinterpret_cast<LONG_PTR>(pcs->lpCreateParams);
                ::SetWindowLongPtrW(
                    hwnd,
                    GWLP_USERDATA,
                    form
                    );
                result = 1;
            }
            else
            {
                AbstractForm** ppform = reinterpret_cast<AbstractForm **>(static_cast<LONG_PTR>(
                    ::GetWindowLongPtrW(
                        hwnd,
                        GWLP_USERDATA
                )));
                bool wasHandled = false;
                if (ppform && *ppform != nullptr)
                {
                    MainForm * form = dynamic_cast<MainForm*>(*ppform);
                    switch (message)
                    {
                    case WM_SIZE:
                        {
                            UINT width = LOWORD(lParam);
                            UINT height = HIWORD(lParam);
                        }
                        result = 0;
                        wasHandled = true;
                        break;
                    case WM_DISPLAYCHANGE:
                        {
                            InvalidateRect(hwnd, NULL, FALSE);
                        }
                        result = 0;
                        wasHandled = true;
                        break;

                    case WM_COMMAND: {
                        switch (LOWORD(wParam))
                        {
                        case component::MenuItemID::EXIT_PROGRAM :
                            PostQuitMessage(0);
                            break;
                        
                        default:
                            break;
                        } 
                        result = 0;
                        wasHandled = true;
                    }
                    break;

                    case WM_CONTEXTMENU:{
                        form->OpenPopMenu(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
                        result = 0;
                        wasHandled = true;
                    }
                    break;

                    case WM_MOUSEMOVE : {
                        if(wParam & MK_LBUTTON){
                            if(form->pControl->dragging_start_pos_buf.x > SHRT_MIN){
                                form->Move(
                                    GET_X_LPARAM(lParam) - form->pControl->dragging_start_pos_buf.x,
                                    GET_Y_LPARAM(lParam) - form->pControl->dragging_start_pos_buf.y
                                );
                            }
                        }
                        result = 0;
                        wasHandled = true;
                    }
                    break;

                    case WM_LBUTTONDBLCLK : {
                        form->ani_manager.Play(form->test_player);
                    case WM_LBUTTONDOWN : 
                        form->pControl->dragging_start_pos_buf = MAKEPOINTS(lParam);
                        form->pControl->last_time_left_click = clock();
                        result = 0;
                        wasHandled = true;
                    }
                    break;

                    case WM_LBUTTONUP: {
                        if(clock() - form->pControl->last_time_left_click < 200){
                            // form->ani_manager.Play(form->pMain_audio_player);
                        }
                        form->Move(
                            GET_X_LPARAM(lParam) - form->pControl->dragging_start_pos_buf.x,
                            GET_Y_LPARAM(lParam) - form->pControl->dragging_start_pos_buf.y
                        );
                        form->pControl->dragging_start_pos_buf.x = SHRT_MIN;
                        form->pControl->dragging_start_pos_buf.y = SHRT_MIN;
                        form->UpdateForm();

                        result = 0;
                        wasHandled = true;
                    }
                    break;

                    case WM_PAINT:
                        {
                            if(!form->ani_manager.IsPlaying()){
                                form->PaintDefault();
                            }
                            else{
                                form->ani_manager.OnPaint();
                            }
                            result = 0;
                            wasHandled = true;
                            break;
                        }

                    case WM_DESTROY:
                        {
                            PostQuitMessage(0);
                        }
                        result = 1;
                        wasHandled = true;
                        break;
                    }
                }
                    
                if (!wasHandled)
                {
                    result = DefWindowProc(hwnd, message, wParam, lParam);
                }
            }
            return result;
        }

        LRESULT CALLBACK MainForm::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){
            LRESULT r = 1;
            try{
                r = MainForm::origin_wnd_proc(hwnd, message, wParam, lParam);
            } catch(ep::BaseException& exception){
                MessageBoxA(hwnd, exception.what(), "Error", MB_ERR_INVALID_CHARS);
            }
            return r;
        }

        void MainForm::OpenPopMenu(int mouse_x, int mouse_y){
            component::MenuInstance instance = this->pComponents->GetRootMenu();
            ::TrackPopupMenuEx(
                instance.GetHandle(),
                TPM_LEFTALIGN 
                | TPM_TOPALIGN 
                | TPM_LEFTBUTTON
                | TPM_VERTICAL,
                mouse_x,
                mouse_y,
                hWnd,
                NULL
            );
        }

        HRESULT MainForm::PaintDefault(){
            HRESULT hr = E_FAIL;
            if(core::display::INSTANCE.HasInitialized() && !core::display::INSTANCE.IsBuilding()){
                ID2D1Bitmap * pbmp;
                hr = core::display::INSTANCE.GetWICBitmapFromId(RES_DEfINE_PNG_author);
                if(!SUCCEEDED(hr)) goto release;
                hr = core::display::INSTANCE.CreateD2DBitmap(&pbmp, pRenderTarget);
                if(!SUCCEEDED(hr)) goto release;
                pRenderTarget->BeginDraw();
                pRenderTarget->DrawBitmap(
                    pbmp,
                    pMain_audio_player->GetRect()
                );
                hr = pRenderTarget->EndDraw();
                ValidateRect(hWnd, NULL);
release:
                tool::SafeRelease(&pbmp);
            }
            return hr;
        }
    }

    
    
}